//
// Generated on Tue Dec 16 2014 12:13:47 GMT+0100 (CET) by Charlie Robbins, Paolo Fragomeni & the Contributors (Using Codesurgeon).
// Version 1.2.6
//

(function(exports) {

	/*
	 * browser.js: Browser specific functionality for director.
	 *
	 * (C) 2011, Charlie Robbins, Paolo Fragomeni, & the Contributors.
	 * MIT LICENSE
	 *
	 */
	if (!Array.isArray) {
		  Array.isArray = function(arg) {
		    return Object.prototype.toString.call(arg) === '[object Array]';
		  };
		}
	if (!Array.prototype.filter)
	{
	  Array.prototype.filter = function(fun /*, thisArg */)
	  {
	    "use strict";

	    if (this === void 0 || this === null)
	      throw new TypeError();

	    var t = Object(this);
	    var len = t.length >>> 0;
	    if (typeof fun !== "function")
	      throw new TypeError();

	    var res = [];
	    var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
	    for (var i = 0; i < len; i++)
	    {
	      if (i in t)
	      {
	        var val = t[i];

	        // NOTE: Technically this should Object.defineProperty at
	        //       the next index, as push can be affected by
	        //       properties on Object.prototype and Array.prototype.
	        //       But that method's new, and collisions should be
	        //       rare, so use the more-compatible alternative.
	        if (fun.call(thisArg, val, i, t))
	          res.push(val);
	      }
	    }

	    return res;
	  };
	}
	var dloc = document.location;

	function dlocHashEmpty() {
		// Non-IE browsers return '' when the address bar shows '#'; Director's logic
		// assumes both mean empty.
		return dloc.hash === '' || dloc.hash === '#';
	}

	var listener = {
		mode: 'modern',
		hash: dloc.hash,
		history: false,

		check: function() {
			var h = dloc.hash;
			if (h != this.hash) {
				this.hash = h;
				this.onHashChanged();
			}
		},

		fire: function() {
			if (this.mode === 'modern') {
				this.history === true ? window.onpopstate() : window.onhashchange();
			} else {
				this.onHashChanged();
			}
		},

		init: function(fn, history) {
			var self = this;
			this.history = history;

			if (!Router.listeners) {
				Router.listeners = [];
			}

			function onchange(onChangeEvent) {
				for (var i = 0, l = Router.listeners.length; i < l; i++) {
					Router.listeners[i](onChangeEvent);
				}
			}

			//note IE8 is being counted as 'modern' because it has the hashchange event
			if ('onhashchange' in window && (document.documentMode === undefined || document.documentMode > 7)) {
				// At least for now HTML5 history is available for 'modern' browsers only
				if (this.history === true) {
					// There is an old bug in Chrome that causes onpopstate to fire even
					// upon initial page load. Since the handler is run manually in init(),
					// this would cause Chrome to run it twise. Currently the only
					// workaround seems to be to set the handler after the initial page load
					// http://code.google.com/p/chromium/issues/detail?id=63040
					setTimeout(function() {
						window.onpopstate = onchange;
					}, 500);
				} else {
					window.onhashchange = onchange;
				}
				this.mode = 'modern';
			} else {
				//
				// IE support, based on a concept by Erik Arvidson ...
				//
				var frame = document.createElement('iframe');
				frame.id = 'state-frame';
				frame.style.display = 'none';
				document.body.appendChild(frame);
				this.writeFrame('');

				if ('onpropertychange' in document && 'attachEvent' in document) {
					document.attachEvent('onpropertychange', function() {
						if (event.propertyName === 'location') {
							self.check();
						}
					});
				}

				window.setInterval(function() {
					self.check();
				}, 50);

				this.onHashChanged = onchange;
				this.mode = 'legacy';
			}

			Router.listeners.push(fn);

			return this.mode;
		},

		destroy: function(fn) {
			if (!Router || !Router.listeners) {
				return;
			}

			var listeners = Router.listeners;

			for (var i = listeners.length - 1; i >= 0; i--) {
				if (listeners[i] === fn) {
					listeners.splice(i, 1);
				}
			}
		},

		setHash: function(s) {
			// Mozilla always adds an entry to the history
			if (this.mode === 'legacy') {
				this.writeFrame(s);
			}

			if (this.history === true) {
				window.history.pushState({}, document.title, s);
				// Fire an onpopstate event manually since pushing does not obviously
				// trigger the pop event.
				this.fire();
			} else {
				dloc.hash = (s[0] === '/') ? s : '/' + s;
			}
			return this;
		},

		writeFrame: function(s) {
			// IE support...
			var f = document.getElementById('state-frame');
			var d = f.contentDocument || f.contentWindow.document;
			d.open();
			d.write("<script>_hash = '" + s + "'; onload = parent.listener.syncHash;<script>");
			d.close();
		},

		syncHash: function() {
			// IE support...
			var s = this._hash;
			if (s != dloc.hash) {
				dloc.hash = s;
			}
			return this;
		},

		onHashChanged: function() {}
	};

	var Router = exports.Router = function(routes) {
		if (!(this instanceof Router)) return new Router(routes);

		this.params = {};
		this.routes = {};
		this.methods = ['on', 'once', 'after', 'before'];
		this.scope = [];
		this._methods = {};

		this._insert = this.insert;
		this.insert = this.insertEx;

		this.historySupport = (window.history != null ? window.history.pushState : null) != null

		this.configure();
		this.mount(routes || {});
	};

	Router.prototype.init = function(r) {
		var self = this,
			routeTo;
		this.handler = function(onChangeEvent) {
			var newURL = onChangeEvent && onChangeEvent.newURL || window.location.hash;
			var url = self.history === true ? self.getPath() : newURL.replace(/.*#/, '');
			self.dispatch('on', url.charAt(0) === '/' ? url : '/' + url);
		};

		listener.init(this.handler, this.history);

		if (this.history === false) {
			if (dlocHashEmpty() && r) {
				dloc.hash = r;
			} else if (!dlocHashEmpty()) {
				self.dispatch('on', '/' + dloc.hash.replace(/^(#\/|#|\/)/, ''));
			}
		} else {
			if (this.convert_hash_in_init) {
				// Use hash as route
				routeTo = dlocHashEmpty() && r ? r : !dlocHashEmpty() ? dloc.hash.replace(/^#/, '') : null;
				if (routeTo) {
					window.history.replaceState({}, document.title, routeTo);
				}
			} else {
				// Use canonical url
				routeTo = this.getPath();
			}

			// Router has been initialized, but due to the chrome bug it will not
			// yet actually route HTML5 history state changes. Thus, decide if should route.
			if (routeTo || this.run_in_init === true) {
				this.handler();
			}
		}

		return this;
	};

	Router.prototype.explode = function() {
		var v = this.history === true ? this.getPath() : dloc.hash;
		if (v.charAt(1) === '/') {
			v = v.slice(1)
		}
		return v.slice(1, v.length).split("/");
	};

	Router.prototype.setRoute = function(i, v, val) {
		var url = this.explode();

		if (typeof i === 'number' && typeof v === 'string') {
			url[i] = v;
		} else if (typeof val === 'string') {
			url.splice(i, v, s);
		} else {
			url = [i];
		}

		listener.setHash(url.join('/'));
		return url;
	};

	//
	// ### function insertEx(method, path, route, parent)
	// #### @method {string} Method to insert the specific `route`.
	// #### @path {Array} Parsed path to insert the `route` at.
	// #### @route {Array|function} Route handlers to insert.
	// #### @parent {Object} **Optional** Parent "routes" to insert into.
	// insert a callback that will only occur once per the matched route.
	//
	Router.prototype.insertEx = function(method, path, route, parent) {
		if (method === "once") {
			method = "on";
			route = function(route) {
				var once = false;
				return function() {
					if (once) return;
					once = true;
					return route.apply(this, arguments);
				};
			}(route);
		}
		return this._insert(method, path, route, parent);
	};

	Router.prototype.getRoute = function(v) {
		var ret = v;

		if (typeof v === "number") {
			ret = this.explode()[v];
		} else if (typeof v === "string") {
			var h = this.explode();
			ret = h.indexOf(v);
		} else {
			ret = this.explode();
		}

		return ret;
	};

	Router.prototype.destroy = function() {
		listener.destroy(this.handler);
		return this;
	};

	Router.prototype.getPath = function() {
		var path = window.location.pathname;
		if (path.substr(0, 1) !== '/') {
			path = '/' + path;
		}
		return path;
	};

	function _every(arr, iterator) {
		for (var i = 0; i < arr.length; i += 1) {
			if (iterator(arr[i], i, arr) === false) {
				return;
			}
		}
	}

	function _flatten(arr) {
		var flat = [];
		for (var i = 0, n = arr.length; i < n; i++) {
			flat = flat.concat(arr[i]);
		}
		return flat;
	}

	function _asyncEverySeries(arr, iterator, callback) {
		if (!arr.length) {
			return callback();
		}
		var completed = 0;
		(function iterate() {
			iterator(arr[completed], function(err) {
				if (err || err === false) {
					callback(err);
					callback = function() {};
				} else {
					completed += 1;
					if (completed === arr.length) {
						callback();
					} else {
						iterate();
					}
				}
			});
		})();
	}

	function paramifyString(str, params, mod) {
		mod = str;
		for (var param in params) {
			if (params.hasOwnProperty(param)) {
				mod = params[param](str);
				if (mod !== str) {
					break;
				}
			}
		}
		return mod === str ? "([._a-zA-Z0-9-%()]+)" : mod;
	}

	function regifyString(str, params) {
		var matches, last = 0,
			out = "";
		while (matches = str.substr(last).match(/[^\w\d\- %@&]*\*[^\w\d\- %@&]*/)) {
			last = matches.index + matches[0].length;
			matches[0] = matches[0].replace(/^\*/, "([_.()!\\ %@&a-zA-Z0-9-]+)");
			out += str.substr(0, matches.index) + matches[0];
		}
		str = out += str.substr(last);
		var captures = str.match(/:([^\/]+)/ig),
			capture, length;
		if (captures) {
			length = captures.length;
			for (var i = 0; i < length; i++) {
				capture = captures[i];
				if (capture.slice(0, 2) === "::") {
					str = capture.slice(1);
				} else {
					str = str.replace(capture, paramifyString(capture, params));
				}
			}
		}
		return str;
	}

	function terminator(routes, delimiter, start, stop) {
		var last = 0,
			left = 0,
			right = 0,
			start = (start || "(").toString(),
			stop = (stop || ")").toString(),
			i;
		for (i = 0; i < routes.length; i++) {
			var chunk = routes[i];
			if (chunk.indexOf(start, last) > chunk.indexOf(stop, last) || ~chunk.indexOf(start, last) && !~chunk.indexOf(stop, last) || !~chunk.indexOf(start, last) && ~chunk.indexOf(stop, last)) {
				left = chunk.indexOf(start, last);
				right = chunk.indexOf(stop, last);
				if (~left && !~right || !~left && ~right) {
					var tmp = routes.slice(0, (i || 1) + 1).join(delimiter);
					routes = [tmp].concat(routes.slice((i || 1) + 1));
				}
				last = (right > left ? right : left) + 1;
				i = 0;
			} else {
				last = 0;
			}
		}
		return routes;
	}

	var QUERY_SEPARATOR = /\?.*/;

	Router.prototype.configure = function(options) {
		options = options || {};
		for (var i = 0; i < this.methods.length; i++) {
			this._methods[this.methods[i]] = true;
		}
		this.recurse = options.recurse || this.recurse || false;
		this.async = options.async || false;
		this.delimiter = options.delimiter || "/";
		this.strict = typeof options.strict === "undefined" ? true : options.strict;
		this.notfound = options.notfound;
		this.resource = options.resource;
		this.history = options.html5history && this.historySupport || false;
		this.run_in_init = this.history === true && options.run_handler_in_init !== false;
		this.convert_hash_in_init = this.history === true && options.convert_hash_in_init !== false;
		this.every = {
			after: options.after || null,
			before: options.before || null,
			on: options.on || null
		};
		return this;
	};

	Router.prototype.param = function(token, matcher) {
		if (token[0] !== ":") {
			token = ":" + token;
		}
		var compiled = new RegExp(token, "g");
		this.params[token] = function(str) {
			return str.replace(compiled, matcher.source || matcher);
		};
		return this;
	};

	Router.prototype.on = Router.prototype.route = function(method, path, route) {
		var self = this;
		if (!route && typeof path == "function") {
			route = path;
			path = method;
			method = "on";
		}
		if (Array.isArray(path)) {
			return path.forEach(function(p) {
				self.on(method, p, route);
			});
		}
		if (path.source) {
			path = path.source.replace(/\\\//ig, "/");
		}
		if (Array.isArray(method)) {
			return method.forEach(function(m) {
				self.on(m.toLowerCase(), path, route);
			});
		}
		path = path.split(new RegExp(this.delimiter));
		path = terminator(path, this.delimiter);
		this.insert(method, this.scope.concat(path), route);
	};

	Router.prototype.path = function(path, routesFn) {
		var self = this,
			length = this.scope.length;
		if (path.source) {
			path = path.source.replace(/\\\//ig, "/");
		}
		path = path.split(new RegExp(this.delimiter));
		path = terminator(path, this.delimiter);
		this.scope = this.scope.concat(path);
		routesFn.call(this, this);
		this.scope.splice(length, path.length);
	};

	Router.prototype.dispatch = function(method, path, callback) {
		var self = this,
			fns = this.traverse(method, path.replace(QUERY_SEPARATOR, ""), this.routes, ""),
			invoked = this._invoked,
			after;
		this._invoked = true;
		if (!fns || fns.length === 0) {
			this.last = [];
			if (typeof this.notfound === "function") {
				this.invoke([this.notfound], {
					method: method,
					path: path
				}, callback);
			}
			return false;
		}
		if (this.recurse === "forward") {
			fns = fns.reverse();
		}

		function updateAndInvoke() {
			self.last = fns.after;
			self.invoke(self.runlist(fns), self, callback);
		}
		after = this.every && this.every.after ? [this.every.after].concat(this.last) : [this.last];
		if (after && after.length > 0 && invoked) {
			if (this.async) {
				this.invoke(after, this, updateAndInvoke);
			} else {
				this.invoke(after, this);
				updateAndInvoke();
			}
			return true;
		}
		updateAndInvoke();
		return true;
	};

	Router.prototype.invoke = function(fns, thisArg, callback) {
		var self = this;
		var apply;
		if (this.async) {
			apply = function(fn, next) {
				if (Array.isArray(fn)) {
					return _asyncEverySeries(fn, apply, next);
				} else if (typeof fn == "function") {
					fn.apply(thisArg, (fns.captures || []).concat(next));
				}
			};
			_asyncEverySeries(fns, apply, function() {
				if (callback) {
					callback.apply(thisArg, arguments);
				}
			});
		} else {
			apply = function(fn) {
				if (Array.isArray(fn)) {
					return _every(fn, apply);
				} else if (typeof fn === "function") {
					return fn.apply(thisArg, fns.captures || []);
				} else if (typeof fn === "string" && self.resource) {
					self.resource[fn].apply(thisArg, fns.captures || []);
				}
			};
			_every(fns, apply);
		}
	};

	Router.prototype.traverse = function(method, path, routes, regexp, filter) {
		var fns = [],
			current, exact, match, next, that;

		function filterRoutes(routes) {
			if (!filter) {
				return routes;
			}

			function deepCopy(source) {
				var result = [];
				for (var i = 0; i < source.length; i++) {
					result[i] = Array.isArray(source[i]) ? deepCopy(source[i]) : source[i];
				}
				return result;
			}

			function applyFilter(fns) {
				for (var i = fns.length - 1; i >= 0; i--) {
					if (Array.isArray(fns[i])) {
						applyFilter(fns[i]);
						if (fns[i].length === 0) {
							fns.splice(i, 1);
						}
					} else {
						if (!filter(fns[i])) {
							fns.splice(i, 1);
						}
					}
				}
			}
			var newRoutes = deepCopy(routes);
			newRoutes.matched = routes.matched;
			newRoutes.captures = routes.captures;
			newRoutes.after = routes.after.filter(filter);
			applyFilter(newRoutes);
			return newRoutes;
		}
		if (path === this.delimiter && routes[method]) {
			next = [
				[routes.before, routes[method]].filter(Boolean)
			];
			next.after = [routes.after].filter(Boolean);
			next.matched = true;
			next.captures = [];
			return filterRoutes(next);
		}
		for (var r in routes) {
			if (routes.hasOwnProperty(r) && (!this._methods[r] || this._methods[r] && typeof routes[r] === "object" && !Array.isArray(routes[r]))) {
				current = exact = regexp + this.delimiter + r;
				if (!this.strict) {
					exact += "[" + this.delimiter + "]?";
				}
				match = path.match(new RegExp("^" + exact));
				if (!match) {
					continue;
				}
				if (match[0] && match[0] == path && routes[r][method]) {
					next = [
						[routes[r].before, routes[r][method]].filter(Boolean)
					];
					next.after = [routes[r].after].filter(Boolean);
					next.matched = true;
					next.captures = match.slice(1);
					if (this.recurse && routes === this.routes) {
						next.push([routes.before, routes.on].filter(Boolean));
						next.after = next.after.concat([routes.after].filter(Boolean));
					}
					return filterRoutes(next);
				}
				next = this.traverse(method, path, routes[r], current);
				if (next.matched) {
					if (next.length > 0) {
						fns = fns.concat(next);
					}
					if (this.recurse) {
						fns.push([routes[r].before, routes[r].on].filter(Boolean));
						next.after = next.after.concat([routes[r].after].filter(Boolean));
						if (routes === this.routes) {
							fns.push([routes["before"], routes["on"]].filter(Boolean));
							next.after = next.after.concat([routes["after"]].filter(Boolean));
						}
					}
					fns.matched = true;
					fns.captures = next.captures;
					fns.after = next.after;
					return filterRoutes(fns);
				}
			}
		}
		return false;
	};

	Router.prototype.insert = function(method, path, route, parent) {
		var methodType, parentType, isArray, nested, part;
		path = path.filter(function(p) {
			return p && p.length > 0;
		});
		parent = parent || this.routes;
		part = path.shift();
		if (/\:|\*/.test(part) && !/\\d|\\w/.test(part)) {
			part = regifyString(part, this.params);
		}
		if (path.length > 0) {
			parent[part] = parent[part] || {};
			return this.insert(method, path, route, parent[part]);
		}
		if (!part && !path.length && parent === this.routes) {
			methodType = typeof parent[method];
			switch (methodType) {
				case "function":
					parent[method] = [parent[method], route];
					return;
				case "object":
					parent[method].push(route);
					return;
				case "undefined":
					parent[method] = route;
					return;
			}
			return;
		}
		parentType = typeof parent[part];
		isArray = Array.isArray(parent[part]);
		if (parent[part] && !isArray && parentType == "object") {
			methodType = typeof parent[part][method];
			switch (methodType) {
				case "function":
					parent[part][method] = [parent[part][method], route];
					return;
				case "object":
					parent[part][method].push(route);
					return;
				case "undefined":
					parent[part][method] = route;
					return;
			}
		} else if (parentType == "undefined") {
			nested = {};
			nested[method] = route;
			parent[part] = nested;
			return;
		}
		throw new Error("Invalid route context: " + parentType);
	};



	Router.prototype.extend = function(methods) {
		var self = this,
			len = methods.length,
			i;

		function extend(method) {
			self._methods[method] = true;
			self[method] = function() {
				var extra = arguments.length === 1 ? [method, ""] : [method];
				self.on.apply(self, extra.concat(Array.prototype.slice.call(arguments)));
			};
		}
		for (i = 0; i < len; i++) {
			extend(methods[i]);
		}
	};

	Router.prototype.runlist = function(fns) {
		var runlist = this.every && this.every.before ? [this.every.before].concat(_flatten(fns)) : _flatten(fns);
		if (this.every && this.every.on) {
			runlist.push(this.every.on);
		}
		runlist.captures = fns.captures;
		runlist.source = fns.source;
		return runlist;
	};

	Router.prototype.mount = function(routes, path) {
		if (!routes || typeof routes !== "object" || Array.isArray(routes)) {
			return;
		}
		var self = this;
		path = path || [];
		if (!Array.isArray(path)) {
			path = path.split(self.delimiter);
		}

		function insertOrMount(route, local) {
			var rename = route,
				parts = route.split(self.delimiter),
				routeType = typeof routes[route],
				isRoute = parts[0] === "" || !self._methods[parts[0]],
				event = isRoute ? "on" : rename;
			if (isRoute) {
				rename = rename.slice((rename.match(new RegExp("^" + self.delimiter)) || [""])[0].length);
				parts.shift();
			}
			if (isRoute && routeType === "object" && !Array.isArray(routes[route])) {
				local = local.concat(parts);
				self.mount(routes[route], local);
				return;
			}
			if (isRoute) {
				local = local.concat(rename.split(self.delimiter));
				local = terminator(local, self.delimiter);
			}
			self.insert(event, local, routes[route]);
		}
		for (var route in routes) {
			if (routes.hasOwnProperty(route)) {
				insertOrMount(route, path.slice(0));
			}
		}
	};

}(typeof exports === "object" ? exports : window));